﻿using DefaultDspPlugins.Common.Modulizer.Engine.Modules.Analog;
using DefaultDspPlugins.Common.Modulizer.Engine.Modules.Waveforms;
using SharpDepend;
using SoundEngine3;
using SoundEngine3.DspPlugins;
using SoundEngine3.DspPlugins.Meta;
using SoundEngine3.Internal;
using SoundEngine3.MIDI.Meta;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.Modules.Container;
using ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.Modules.Converters;
using ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.Modules.GUI;
using ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.Modules.HighLevel;
using ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.Modules.LowLevel;
using ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.Modules.LowLevel.Midi;
using ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.Modules.Protected;
using ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.Waveforms;

namespace ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine
{
    /// <summary>
    /// Manager modules to build a complete synthesizer.
    /// </summary>
    public class ModuleManager : GroupModule
    {
        public enum ModulizerType
        {
            Synthesizer,
            Effect
        }

        internal List<BaseModule> mAllModules = new List<BaseModule>();
        private List<BaseModule> mProcessedModules = new List<BaseModule>();
        private List<BaseModule> mNonProcessedModules = new List<BaseModule>();
        private UniqueIdentityManager<BaseModule> mAllModulesIDs;
        private IStopwatch mStopwatch;
        private ModulizerType mType;

        internal UniqueIdentityManager<ParameterProperties> mUniqueParameters;

        internal readonly InModule AudioInput;
        internal readonly MidiInModule MidiInput;
        internal readonly OutModule Out;

        public readonly ReadOnlyCollection<BaseModule> Modules;

        internal BaseDspPluginInstance DspOwner { get; private set; }
        
        public ModuleManager(SoundEngineContext soundEngine, BaseDspPluginInstance dspOwner, ModulizerType type, UniqueIdentityManager<ParameterProperties> uniqueParameters)
            : base(soundEngine)
        {
            mUniqueParameters = uniqueParameters;

            mType = type;

            DspOwner = dspOwner;
            
            SetManager(this);

            mStopwatch = SharpDepend.Manager.StopwatchManager.CreateStopwatch();

            mAllModulesIDs = new UniqueIdentityManager<BaseModule>();

            Modules = new ReadOnlyCollection<BaseModule>(mAllModules);
            
            AddModule(this);

            switch (type)
            {
                case ModulizerType.Synthesizer:
                    // Add protected modules.
                    MidiInput = new MidiInModule(this);
                    Out = new OutModule(this);
                    AddSubModule(MidiInput);
                    AddSubModule(Out);
                    break;
                case ModulizerType.Effect:
                    // Add protected modules.
                    AudioInput = new InModule(this);
                    Out = new OutModule(this);
                    AddSubModule(AudioInput);
                    AddSubModule(Out);
                    break;
                default:
                    break;
            }

            // Containers.
            ModulesRegistration.RegisterModule<GroupModule>();

            // Waveforms.
            ModulesRegistration.RegisterModule<BasicWaveformsModule>();
            ModulesRegistration.RegisterModule<CustomWaveFormModule>();

            // Low level.
            ModulesRegistration.RegisterModule<SpreadModule>();
            ModulesRegistration.RegisterModule<InvertModule>();
            ModulesRegistration.RegisterModule<ModulusModule>();
            ModulesRegistration.RegisterModule<PowerModule>();
            ModulesRegistration.RegisterModule<MultiplyModule>();
            ModulesRegistration.RegisterModule<DivideModule>();
            ModulesRegistration.RegisterModule<SubtactionModule>();
            ModulesRegistration.RegisterModule<AddModule>();
            ModulesRegistration.RegisterModule<Add4Module>();
            ModulesRegistration.RegisterModule<ExpModule>();
            ModulesRegistration.RegisterModule<CustomAlgorithmModule>();

            // High level.
            ModulesRegistration.RegisterModule<MixModule>();
            ModulesRegistration.RegisterModule<SidechaningModule>();
            ModulesRegistration.RegisterModule<LFOModule>();
            ModulesRegistration.RegisterModule<MonoAmpModule>();
            ModulesRegistration.RegisterModule<OscilatorModule>();
            ModulesRegistration.RegisterModule<ResonantModule>();
            ModulesRegistration.RegisterModule<EnvelopeModule>();
            ModulesRegistration.RegisterModule<EnvelopeDRModule>();
            ModulesRegistration.RegisterModule<GlideModule>();
            ModulesRegistration.RegisterModule<FlangerModule>();
            ModulesRegistration.RegisterModule<DecimalModule>();
            ModulesRegistration.RegisterModule<LowPassModule>();
            ModulesRegistration.RegisterModule<HighPassModule>();

            // GUI.
            ModulesRegistration.RegisterModule<Parameter1Module>();

            // Midi.
            ModulesRegistration.RegisterModule<MidiConverterModule>();
            ModulesRegistration.RegisterModule<Voice4Module>();
            ModulesRegistration.RegisterModule<PitchSpreadModule>();
            ModulesRegistration.RegisterModule<MidiOctaveSplitterModule>();
            ModulesRegistration.RegisterModule<MidiOctaveModModule>();
            ModulesRegistration.RegisterModule<MidiAddModule>();
            ModulesRegistration.RegisterModule<MidiPitchModule>();

            //// Analog
            //ModulesRegistration.RegisterModule<DiodModule>();
            //ModulesRegistration.RegisterModule<CapacitorModule>();

        }

        /// <summary>
        /// DO NOT USE THIS METHOD. THIS IS ONLY ME WHO HAS ACCESS TO THIS!
        /// <para>Use InterateProcess() instead.</para>
        /// </summary>
        public override void Process()
        {
            // Do nothing. Use ProcessedBuffer property to get the result.
        }

        /// <summary>
        /// Calls when a module is added.
        /// </summary>
        public event Action<BaseModule> OnAddedModule;

        /// <summary>
        /// Calls when a module is removed.
        /// </summary>
        public event Action<BaseModule> OnRemovedModule;

        /// <summary>
        /// Load a module to manager.
        /// </summary>
        /// <param name="module">Module to add.</param>
        internal void AddModule(BaseModule module)
        {
            lock (SoundEngineContext.SoundLocker)
            {
                if (module == null)
                {
                    throw new ArgumentNullException(nameof(module));
                }
                else if (mAllModules.Contains(module))
                {
                    throw new Exception("Module " + module.Name + " already added.");
                }

                mAllModulesIDs.GetNewIdentity(module);

                mAllModules.Add(module);

                var call = OnAddedModule;
                if (call != null)
                {
                    call(module);
                }
            }
        }

        /// <summary>
        /// Remove a module.
        /// </summary>
        /// <param name="module">Module to remove.</param>
        internal void RemoveModule(BaseModule module)
        {
            lock (SoundEngineContext.SoundLocker)
            {
                if (module == null)
                {
                    throw new ArgumentNullException(nameof(module));
                }
                else if (!mAllModules.Contains(module))
                {
                    throw new Exception("Module " + module.Name + " do not exist. Can not remove.");
                }
                else if (IsProtectedModule(module))
                {
                    throw new Exception("Can not remove protected module.");
                }

                var call = OnRemovedModule;
                if (call != null)
                {
                    call(module);
                }

                module.ClearConnectedInputs();
                module.ClearConnectedOutputs();

                mAllModulesIDs.ReleaseIdentity(module);

                mAllModules.Remove(module);

                // If module still in the processed list. Remove it.
                if (mProcessedModules.Contains(module))
                {
                    mProcessedModules.Remove(module);
                } 
            }
        }

        /// <summary>
        /// Clear all modules.
        /// </summary>
        public void ClearAllModules()
        {
            if (MidiInput != null)
            {
                MidiInput.ClearConnectedInputs();
                MidiInput.ClearConnectedOutputs();
            }
            ClearConnectedInputs();
            ClearConnectedOutputs();

            // Remove sub modules.
            ClearSubModulesFrom(this);

            for (int i = mAllModules.Count - 1; i >= 0; i--)
            {
                var module = mAllModules[i];

                if (!IsProtectedModule(module))
                {
                    RemoveModule(module);
                }
            }
        }

        public void ClearSubModulesFrom(GroupModule groupModule)
        {
            // Remove sub modules.
            for (int i = groupModule.SubModules.Count - 1; i >= 0; i--)
            {
                var module = groupModule.SubModules[i];
                if (module is GroupModule)
                {
                    ClearSubModulesFrom(module as GroupModule);
                }

                if (!IsProtectedModule(module))
                {
                    groupModule.RemoveSubModule(module);
                }
            }
        }

        public bool IsProtectedModule(BaseModule module)
        {
            return module == this || module == MidiInput || module == Out || module == AudioInput;
        }

        public IEnumerable<BaseModule> NotPressecedModules
        {
            get
            {
                List<BaseModule> modules = new List<BaseModule>(mAllModules);
                foreach (var item in mProcessedModules)
                {
                    modules.Remove(item);
                }
                return modules;
            }
        }

        /// <summary>
        /// Start to process every module in a specific order.
        /// </summary>
        public void InterateProcess()
        {
            // Follow the outputs to other modules and process them in a sorted way.
            // Start process modules that has complete input process or start emidetly if no inputs at all.

            // Clear all processed modules, now it is a new iteration.
            ClearProcessedModules();

            // Loop until all modules are processed.
            //while (mAllModules.Count != mProcessedModules.Count)
            while (mNonProcessedModules.Count > 0)
            {
                // Loop all modules.
                for (int i = mNonProcessedModules.Count - 1; i >= 0; i--)
                {
                    TryProcessModule(mNonProcessedModules[i]);
                }
            }
        }

        internal BaseModule _GetModuleByTemporaryId(long temporaryId, BaseModule ignoreModule)
        {
            foreach (var module in mAllModules)
            {
                if (module.GetTemporaryCloneId() == temporaryId && module != ignoreModule)
                {
                    return module;
                }
            }

            throw new Exception("Couln'd not get module by temporary id.");
        }

        private void TryProcessModule(BaseModule module)
        {
            // If module is already completed, skip it.
            if (!mProcessedModules.Contains(module))
            {
                // Check if module has complete inputs, then process it.
                if (module.CompleteInputs())
                {
                    ProcessModule(module);
                }

                module.Checked++;
            }
        }

        private void ClearProcessedModules()
        {
            foreach (var module in mProcessedModules)
            {
                module.SetProcessed(false);
            }
            mProcessedModules.Clear();

            mNonProcessedModules.AddRange(mAllModules);
        }

        private void ProcessModule(BaseModule module)
        {
            mProcessedModules.Add(module);
            mNonProcessedModules.Remove(module);

            module.Process();
            module.SetProcessed(true);
        }

        protected override void OnProcessEvents(List<MidiMessage> midiMessages)
        {
            if (MidiInput != null)
            {
                MidiInput.ProcessEvents(midiMessages);
            }
        }

        public BaseModule Find(int moduleId)
        {
            return mAllModulesIDs.Find(moduleId);
        }
        
        public void ConfigID(BaseModule module, int id)
        {
            mAllModulesIDs.AddExistedIndentity(id, module);
        }
    }
}
